package com.code.aspect;

import com.code.common.enums.ResultCode;
import com.code.common.exceptions.BusinessException;
import com.code.common.util.IpUtil;
import com.code.common.web.annotations.RateLimit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

/**
 * 限流切面
 *
 * @author tangyifei
 **/
@Aspect
@Configuration
@Slf4j
public class LimitAspect {

    private static final String SEPARATOR = "-";

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private DefaultRedisScript<Number> redisLuaScript;

    @Before("execution(* com.code.web ..*(..) )")
    public void interceptor(JoinPoint joinPoint) {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        RateLimit rateLimit = method.getAnnotation(RateLimit.class);

        if (null != rateLimit) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ipAddress = IpUtil.getRealIp(request);
            StringBuffer stringBuffer = new StringBuffer(1 << 3);
            stringBuffer.append(ipAddress).append(SEPARATOR)
                    .append(targetClass.getName()).append(SEPARATOR)
                    .append(method.getName()).append(SEPARATOR)
                    .append(rateLimit.key());

            List<String> keys = Collections.singletonList(stringBuffer.toString());

            Number number = redisTemplate.execute(redisLuaScript, keys, rateLimit.count(), rateLimit.time());

            if (null != number && number.intValue() != 0 && number.intValue() <= rateLimit.count()) {
                log.info("来源ip：{}，限流时间段内访问第：{} 次，调用的uri：{}", ipAddress, number.toString(), request.getRequestURI());
                return;
            }
            throw new BusinessException(ResultCode.REQUEST_IS_TOO_HIGH);
        }
    }

}
